﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Seagull.DAL
{
    public class UdpStream : Stream
    {
        public UdpClient Client { get; set; }

        public UdpStream(UdpClient client)
        {
            Client = client;
        }

        public override void Flush()
        {
            throw new NotSupportedException();
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException();
        }

        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        private byte[] _availableData;

        public override int Read(byte[] buffer, int offset, int count)
        {
            if(buffer == null)
                throw new ArgumentNullException("buffer");

            if(offset < 0 || count < 0 || offset + count > buffer.Length)
            {
                throw new ArgumentOutOfRangeException("",
                    string.Format("offset: {0}, count: {1}, buffer length: {2}",
                        offset, count, buffer.Length));
            }

            int currentOffset = offset;
            int countToRead = count;

            if(_availableData != null)
            {
                ConsumeAvailableData(buffer, ref currentOffset, ref countToRead);
            }
            byte[] received = null;
            int consumed = 0;
            while(countToRead > 0)
            {
                ReceiveData(buffer, ref currentOffset, ref countToRead, out received, out consumed);
            }
            if(received != null && consumed < received.Length)
            {
                _availableData = new byte[received.Length - consumed];
                Array.Copy(received, consumed, _availableData, 0, _availableData.Length);
            }
            return count;
        }

        private void ReceiveData(byte[] buffer, ref int currentOffset, ref int countToRead,
            out byte[] received, out int consumed)
        {
            var remoteEp = new IPEndPoint(IPAddress.Any, 0);
            received = Client.Receive(ref remoteEp);
            consumed = Math.Min(received.Length, countToRead);
            Array.Copy(received, 0, buffer, currentOffset, consumed);
            currentOffset += consumed;
            countToRead -= consumed;
        }

        private void ConsumeAvailableData(byte[] buffer, ref int currentOffset, ref int countToRead)
        {
            var consumedCount = Math.Min(_availableData.Length, countToRead);
            Array.Copy(_availableData, 0, buffer, currentOffset, consumedCount);
            currentOffset += consumedCount;
            countToRead -= consumedCount;
            if(consumedCount == _availableData.Length)
            {
                _availableData = null;
            }
            else
            {
                var notConsumedBytes = new byte[_availableData.Length - consumedCount];
                Array.Copy(_availableData, consumedCount, notConsumedBytes, 0, notConsumedBytes.Length);
                _availableData = notConsumedBytes;
            }
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }

        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return false; }
        }

        public override bool CanWrite
        {
            get { return false; }
        }

        public override long Length
        {
            get { throw new NotSupportedException(); }
        }

        public override long Position
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }

        protected override void Dispose(bool disposing)
        {
            if(disposing)
            {
                if(Client != null)
                {
                    Client.Close();
                    Client = null;
                }
            }
            base.Dispose(disposing);
        }
    }
}
